package klimuts.periodicals.data;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import klimuts.periodicals.admin.Order;
import klimuts.periodicals.admin.actions.AddPeriodical;
import klimuts.periodicals.admin.actions.ShowOrders;
import klimuts.periodicals.admin.actions.ShowUser;
import klimuts.periodicals.admin.actions.ShowUsers;
import klimuts.periodicals.admin.actions.UpdateOrder;
import klimuts.periodicals.catalog.Catalog;
import klimuts.periodicals.catalog.Periodical;
import klimuts.periodicals.catalog.actions.ShowCatalog;
import klimuts.periodicals.user.Cart;
import klimuts.periodicals.user.User;
import klimuts.periodicals.user.actions.Login;
import klimuts.periodicals.user.actions.PlaceOrder;
import klimuts.periodicals.user.actions.Registration;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
/**
 * Provides methods for place and retrieving information from DB.
 * @author Siarhei Klimuts
 */
public class DataSource {
	private static final SessionFactory sessionFactory = buildSessionFactory();

	/**
	 * Create the {@link SessionFactory} from hibernate.cfg.xml once before
	 * place or retrieving some data. 
	 * @return Return new instance of {@link SessionFactory}.
	 */
    private static SessionFactory buildSessionFactory() {
        try {
        	Configuration configuration = new Configuration().configure();
        	ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();     
    		return configuration.buildSessionFactory(serviceRegistry);
        }
        catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
    
    /**
     * {@link Login} action use this method for retrieving {@link User} object by login and password
     * @param login - {@link String} user login
     * @param password - {@link String} user password
     * @return Return {@link User} object if exist user who login and password
     * same as provided. Return <code>null</code> if user not found.
     * @see klimuts.periodicals.user.actions.Login
     */
	static public User getUser(String login, String password){
		User result=null;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=(User)session.createQuery("select new User(id, rights) from User where login=:login and password=:password")
					.setString("login", login)
					.setString("password", password)
					.uniqueResult();
			result.setLogin(login);
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=null;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * Method for check provided login is free.
	 * @param login - user login
	 * @return Return <code>true</code> if database contains user with provided login or <code>false</code> if that login not exists.
	 */
	static public boolean getUser(String login){
		boolean result=false;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			long id=0;
			id=(long)session.createQuery("select id from User where login=:login")
					.setString("login", login)
					.uniqueResult();
			result=(id>0);
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=false;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * {@link ShowUser} action using this method for retrieving information about provided user  
	 * @param id - user id
	 * @return Return {@link User} object by provided id or <code>null</code> if some error occurred
	 * @see klimuts.periodicals.admin.actions.ShowUser
	 */
	static public User getUser(long id){
		User result=null;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=(User)session.get(User.class, id);
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=null;
		} finally {
			session.close();
		}
		return result;
	}

	/**
	 * {@link ShowUsers} action using this method for retrieving list of all users in DB. <br>
	 * Method using short constructor of {@link User} class
	 * @return Return {@link LinkedList} contains all users or <code>null</code> if some error is occurred
	 * @see klimuts.periodicals.admin.actions.ShowUsers
	 */
	static public LinkedList<User> getUsers(){
		LinkedList<User> result=null;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=new LinkedList<User>(session.createQuery("select new User(id, login) from User").list());
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=null;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * {@link Registration} action using this method for adding new user to DB
	 * @param user - {@link User} object
	 * @return Return {@link User} object if transaction success or <code>null</code> if some error occurred
	 * @see klimuts.periodicals.user.actions.Registration
	 */
	static public User addUser(User user){
		User result=null;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			session.save(user);
			transaction.commit();
			result=user;
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=null;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * {@link AddPeriodical} action using this method for adding new periodical to DB
	 * @param periodical - {@link Periodical} object
	 * @return Return <code>true</code> if transaction success or <code>false</code> if some error occurred
	 * @see klimuts.periodicals.admin.actions.AddPeriodical
	 */
	static public boolean addPeriodical(Periodical periodical){
		boolean result=false;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			session.save(periodical);
			transaction.commit();
			result=true;
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=false;
		} finally {
			session.close();
		}
		return result;
	}	
	
	/**
	 * {@link ShowCatalog} action using this method for retrieving new periodical from DB
	 * @param periodical - {@link Periodical} object
	 * @return Return true if transaction success or false if some error occurred
	 * @see klimuts.periodicals.catalog.actions.ShowCatalog
	 */
	static public Catalog getPeriodicals(){
		Catalog result=null;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=new Catalog(session.createQuery("from Periodical").list());
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=null;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * {@link ShowCatalog} action using this method with entity="Periodical" for
	 * retrieving max id from periodicals table.<br>
	 * {@link ShowOrders} action using this method with entity="Order" for
	 * retrieving max id from carts table.
	 * @param entity - {@link String} "Order" or "Periodical"
	 * @return Return max id of provided entity in table
	 * @see klimuts.periodicals.catalog.actions.ShowCatalog
	 * @see klimuts.periodicals.admin.actions.ShowOrders
	 */
	static public long getMax(String entity){
		long result=0;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=(long)session.createQuery("select max(id) from "+entity).uniqueResult();
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=0;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * {@link PlaceOrder} action using this method for adding new order to DB 
	 * @param cart - {@link Cart} object
	 * @param owner - {@link User} object
	 * @return Return id of provided order
	 * @see klimuts.periodicals.user.actions.PlaceOrder
	 */
	static public int addOrder(Cart cart, User owner) {
		int result=0;
		Order order=new Order(owner, cart.getPrice(), new Date(), (byte)0);
		order.setPeriodicals((List)cart);
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=session.save(order).hashCode();
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=0;
		} finally {
			session.close();
		}
		return result;
	}

	/**
	 * {@link ShowOrders} action using this method for retrieving orders list from DB 
	 * @return Return {@link LinkedList} of {@link Order} objects
	 * @see klimuts.periodicals.admin.actions.ShowOrders
	 */
	static public LinkedList<Order> getOrders(){
		LinkedList<Order> result=null;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			result=new LinkedList<Order>(session.createQuery("from Order").list());
			transaction.commit();
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=null;
		} finally {
			session.close();
		}
		return result;
	}
	
	/**
	 * {@link UpdateOrder} action using this method for updating order status in DB 
	 * @param id - order id
	 * @param status - new order status
	 * @return Return <code>true</code> if status updated or <code>false</code> if an error occurred
	 * @see klimuts.periodicals.admin.actions.UpdateOrder
	 */
	static public boolean orderUpdate(long id, byte status){
		boolean result=false;
		Session session=sessionFactory.openSession();
		Transaction transaction=null;
		try {
			transaction=session.beginTransaction();
			//Reduced query by native update without order load
			session.createQuery("update Order set status=:status where id=:id")
				.setByte("status", status)
				.setLong("id", id)
				.executeUpdate();
			transaction.commit();
			result=true;
		} catch (Exception e) {
			if (transaction!=null)
				transaction.rollback();
			result=false;
		} finally {
			session.close();
		}
		return result;
	}	

}
